package com.lc.command;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by zhyioo on 2017/8/7.
 */

public class CommandDispatcher implements Runnable {
    public static String KEY_ARGUMENTS = "Arguments";
    public static String KEY_ISBACKGROUND = "IsBackground";
    public static String KEY_TASKTIME = "TaskTime";
    public static String KEY_WAKEUPTIME = "WakeUpTime";
    public static String KEY_ISOK = "IsOK";

    public static boolean IsTaskOK(JSONObject arg) {
        return arg.optBoolean(KEY_ISOK, false);
    }

    public static boolean SetBackground(JSONObject arg, boolean isBackground) {
        boolean lastVal = arg.optBoolean(KEY_ISBACKGROUND, false);
        if(lastVal != isBackground) {
            try {
                arg.put(KEY_ISBACKGROUND, isBackground);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        return lastVal;
    }

    public JSONObject GetArguments(JSONObject arg) {
        return arg.optJSONObject(KEY_ARGUMENTS);
    }

    protected class CommandTask {
        public ICommand Work = null;
        public ICommand Callback = null;
        public JSONObject Arguments = new JSONObject();
        public boolean IsBackground = false;
        public long TaskTime = 0;
        public long WakeUpTimeout = 0;
    }

    protected final Lock CommandLock = new ReentrantLock();

    protected Thread _workThread = null;
    protected Condition _lock = CommandLock.newCondition();
    protected BlockingQueue<CommandTask> _taskQueue = null;

    public CommandDispatcher() {
        _taskQueue = new LinkedBlockingDeque<>();
        _workThread = new Thread(this);
        _workThread.start();
    }

    protected void OnWork() {
        int taskIndex = 0;
        long wakeupTime = 0xFFFFFFFF;

        if (_taskQueue.size() < 1) {
            try {
                _lock.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            CommandTask task = _taskQueue.take();
            ++taskIndex;
            if (task.Work == null) {
                return;
            }

            if (task.IsBackground && task.WakeUpTimeout > 0) {
                long currentTick = System.currentTimeMillis();
                // 未到唤醒时间
                if (currentTick < task.TaskTime) {
                    wakeupTime = Math.min(wakeupTime, task.TaskTime - currentTick);
                    _taskQueue.put(task);
                    return;
                }
                wakeupTime = Math.min(wakeupTime, task.WakeUpTimeout);
                task.TaskTime = (currentTick + task.WakeUpTimeout);
            }

            JSONObject rlt = new JSONObject();
            boolean bOK = task.Work.Run(task.Arguments, rlt);
            if (task.Callback != null) {
                JSONObject arg = new JSONObject();
                try {
                    arg.put(KEY_ISBACKGROUND, task.IsBackground);
                    arg.put(KEY_TASKTIME, task.TaskTime);
                    arg.put(KEY_WAKEUPTIME, task.WakeUpTimeout);
                    arg.put(KEY_ARGUMENTS, task.Arguments);
                    arg.put(KEY_ISOK, bOK);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                if (task.Callback.Run(arg, rlt)) {
                    task.IsBackground = arg.optBoolean(KEY_ISBACKGROUND, task.IsBackground);
                    task.TaskTime = arg.optLong(KEY_TASKTIME, task.TaskTime);
                    task.WakeUpTimeout = arg.optLong(KEY_WAKEUPTIME, task.WakeUpTimeout);

                    JSONObject fallbackArg = arg.optJSONObject(KEY_ARGUMENTS);
                    if (fallbackArg != null) {
                        task.Arguments = fallbackArg;
                    }
                }
            }
            if (task.IsBackground) {
                _taskQueue.put(task);

                // 任务已经执行完一遍
                if (taskIndex >= _taskQueue.size()) {
                    taskIndex = 0;
                    if (wakeupTime > 0) {
                        _lock.await(wakeupTime, TimeUnit.MILLISECONDS);
                    }
                    wakeupTime = 0xFFFFFFFF;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        int taskIndex = 0;
        long wakeupTime = 0xFFFFFFFF;

        while(true) {
            CommandLock.lock();
            try {
                if (_taskQueue.size() < 1) {
                    try {
                        _lock.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                CommandTask task = _taskQueue.take();
                ++taskIndex;
                if (task.Work == null) {
                    continue;
                }

                if (task.IsBackground && task.WakeUpTimeout > 0) {
                    long currentTick = System.currentTimeMillis();
                    // 未到唤醒时间
                    if (currentTick < task.TaskTime) {
                        wakeupTime = Math.min(wakeupTime, task.TaskTime - currentTick);
                        _taskQueue.put(task);
                        continue;
                    }
                    wakeupTime = Math.min(wakeupTime, task.WakeUpTimeout);
                    task.TaskTime = (currentTick + task.WakeUpTimeout);
                }

                JSONObject rlt = new JSONObject();
                boolean bOK = task.Work.Run(task.Arguments, rlt);
                if (task.Callback != null) {
                    JSONObject arg = new JSONObject();
                    try {
                        arg.put(KEY_ISBACKGROUND, task.IsBackground);
                        arg.put(KEY_ARGUMENTS, task.Arguments);
                        arg.put(KEY_ISOK, bOK);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                    if (task.Callback.Run(arg, rlt)) {
                        task.IsBackground = arg.optBoolean(KEY_ISBACKGROUND, task.IsBackground);
                        JSONObject fallbackArg = arg.optJSONObject(KEY_ARGUMENTS);
                        if (fallbackArg != null) {
                            task.Arguments = fallbackArg;
                        }
                    }
                }
                if (task.IsBackground) {
                    _taskQueue.put(task);

                    // 任务已经执行完一遍
                    if (taskIndex >= _taskQueue.size()) {
                        taskIndex = 0;
                        if (wakeupTime > 0) {
                            _lock.await(wakeupTime, TimeUnit.MILLISECONDS);
                        }
                        wakeupTime = 0xFFFFFFFF;
                    }
                }
            } catch (Exception e) {

            } finally {
                CommandLock.unlock();
            }
        }
    }

    public void Cancel() {
        _workThread.interrupt();
        _taskQueue.clear();
    }

    protected boolean _Async(ICommand work, JSONObject arg, ICommand callback, boolean isBackground, long wakeupTime) {
        if(work == null) {
            return false;
        }

        CommandLock.lock();
        boolean bOK = false;
        try {
            CommandTask task = new CommandTask();
            task.Work = work;
            task.Callback = callback;
            task.Arguments = arg;
            task.IsBackground = isBackground;
            if (isBackground) {
                task.TaskTime = System.currentTimeMillis();
                task.WakeUpTimeout = wakeupTime;
            } else {
                task.TaskTime = 0;
                task.WakeUpTimeout = 0;
            }

            try {
                _lock.signalAll();
                _taskQueue.put(task);
                bOK = true;
            } catch (InterruptedException e) {
            }
        } finally {
            CommandLock.unlock();
        }
        return bOK;
    }

    public boolean Async(ICommand work, JSONObject arg, ICommand callback) {
        return _Async(work, arg, callback, false, 0);
    }

    public boolean Background(ICommand work, JSONObject arg, ICommand callback) {
        return Background(work, arg, callback, 0);
    }

    public boolean Background(ICommand work, JSONObject arg, ICommand callback, long wakeupTime) {
        return _Async(work, arg, callback, true, wakeupTime);
    }
}
